_package("alz.gesture");

_import("alz.gesture.AbstractManager");
_import("alz.gesture.Touch");
_import("alz.gesture.Tap");
_import("alz.gesture.Swipe");
_import("alz.gesture.Drag");
_import("alz.gesture.Pinch");
_import("alz.util.Point");
_import("alz.util.EventSimulator");

/**
 * 手势管理
 */
_class("GestureManager", AbstractManager, function(){
	this.eventNames = {
		"start": "touchstart",
		"move": "touchmove",
		"end": "touchend"
	};
	this.defaultPreventedMouseEvents = ["click"];
	this.clickMoveThreshold = 5;
	this.getId = function(){
		return this._id;
	};
	this._init = function(){
		_super._init.call(this);
		this._id = "";
		this._body = null;
		this.startEvent = null;
		this.startPoint = null;
		this.targets = [];
		this.followTouches = [];
		this.currentGestures = [];
		this.currentTargets = [];
		this.listenerWrappers = {};
		this.eventSimulator = null;
		this.locks = {};
		this.lastMovePoint = null;
		this.isClick = false;
	};
	this.create = function(id, app){
		this._id = id;
		//_super.create.apply(this, arguments);
		this._body = document.body;
		this.registerType("touch", Touch);
		this.registerType("tap"  , Tap);
		this.registerType("swipe", Swipe);
		this.registerType("drag" , Drag);
		this.registerType("pinch", Pinch);
		this.targets = [];
		this.followTouches = [];
		this.currentGestures = [];
		this.currentTargets = [];
		if(!runtime.isTouch){
			this.eventNames["start"] = "mousedown";
			this.eventNames["move"] = "mousemove";
			this.eventNames["end"] = "mouseup";
		}
		this.listenerWrappers = {
			"start": runtime.createDelegate(this, "onTouchStart"),
			"move" : runtime.createDelegate(this, "onTouchMove"),
			"end"  : runtime.createDelegate(this, "onTouchEnd"),
			"mouse": runtime.createDelegate(this, "onMouseEvent")
		};
		this.attachListeners();
	};
	this.dispose = function(){
		this._body = null;
		_super.dispose.apply(this);
	};
	this.getEventSimulator = function(){
		if(!this.eventSimulator){
			this.eventSimulator = new EventSimulator();
		}
		return this.eventSimulator;
	};
	this.attachListeners = function(){
		if(runtime.ie){
			for(var key in this.eventNames){
				this._body.attachEvent("on" + this.eventNames[key], this.listenerWrappers[key]);
			}
		}else{
			for(var key in this.eventNames){
				this._body.addEventListener(this.eventNames[key], this.listenerWrappers[key], false);
			}
		}
		if(runtime.isTouch){
			this.defaultPreventedMouseEvents.forEach(function(name){
				this._body.addEventListener(name, this.listenerWrappers["mouse"], true);
			}, this);
		}
	};
	this.detachListeners = function(){
		for(var key in this.eventNames){
			this._body.removeEventListener(this.eventNames[key], this.listenerWrappers[key], false);
		}
		if(runtime.isTouch){
			this.defaultPreventedMouseEvents.forEach(function(name){
				this.body.removeEventListener(name, this.listenerWrappers["mouse"], true);
			}, this);
		}
	};
	this.onMouseEvent = function(ev){
		if(!ev.isSimulated){
			ev.preventDefault();
			ev.stopPropagation();
		}
	};
	var hash_focus = {
		"a"       : true,
		"input"   : true,
		"button"  : true,
		"textarea": true,
		"select"  : true
	};
	this.onTouchStart = function(ev){
		//runtime.log("GestureManager::onTouchStart");
		if(ev.stopped === true) return;
		var targets = [];
		var target = ev.target;
		if(!runtime.is.iOS){
			/*
			if(!(target.tagName && ["input", "textarea", "select"].indexOf(target.tagName.toLowerCase()) !== -1)){
				ev.preventDefault();
			}
			*/
			if(!target.tagName){
				ev.preventDefault();
			}else{
				var tag = target.tagName.toLowerCase();
				/*
				 * 图片必须阻止事件传播，不然图片就会被拖动
				 * 所有具有焦点的元素应该正常获取焦点，无论是否具有action特性
				 * 其他有action特性的元素不应该阻止事件
				 */
				//if(tag == "input" && target.type == "button" || !(tag in hash_focus)){
				if(!(tag in hash_focus) && !target.getAttribute("_action") || tag == "img"){
					ev.preventDefault();  //[TODO]这句会影响焦点元素的正常切换，间接影响输入法的隐藏逻辑
				}
			}
		}
		// There's already a touchstart without any touchend!
		// This used to happen on HTC Desire and HTC Incredible
		// We have to clean it up
		if(this.startEvent){
			this.onTouchEnd(ev);
		}
		this.locks = {};
		this.currentTargets = [];
		//var arr = [];
		for(; target && target != this._body; target = target.parentNode){
			if(this.targets.indexOf(target) !== -1){
				targets.unshift(target);
			}
			this.currentTargets.push(target);
			//if(target._ptr){
			//	arr.push(target._ptr);
			//}
		}
		/*
		for(var i = 0, len = arr.length; i < len; i++){
			var sb = [];
			sb.push("[" + arr[i]._className + "]");
			//输出两种不同的事件
			for(var k in arr[i]){
				//if(arr[i].hasOwnProperty(k){
				if(k.substr(0, 2) == "on"){
					sb.push(k);
				}
			}
			for(var key in arr[i]._listeners){
				sb.push(key);
			}
			console.log(sb.join(","));
		}
		*/
		this.startEvent = ev;
		this.startPoint = Point.fromEvent(ev);
		this.lastMovePoint = null;
		this.isClick = true;
		this.handleTargets(targets, ev);
	};
	this.onTouchMove = function(ev){
		if(!this.startEvent) return;
		//runtime.log("GestureManager::onTouchMove");
		if(runtime.is.Desktop){
			ev.target = this.startEvent.target;
		}
		if(runtime.is.iOS){
			ev.preventDefault();
		}
		this.lastMovePoint = Point.fromEvent(ev);
		if(runtime.isTouch && this.isClick && !this.lastMovePoint.isWithin(this.startPoint, this.clickMoveThreshold)){
			this.isClick = false;
		}
		var touch = ev.changedTouches ? ev.changedTouches[0] : ev;
		var gestures = this.currentGestures;
		for(var i = 0; i < gestures.length; i++){  //gestures.length会变化
			if(ev.stopped){
				break;
			}
			var gesture = gestures[i];
			if(gesture.listenForMove){
				gesture.onTouchMove(ev, touch);
			}
		}
	};
	/**
	 * This listener is here to always ensure we stop all current gestures
	 * @private
	 */
	this.onTouchEnd = function(ev){
		//runtime.log("GestureManager::onTouchEnd");
		var gestures = this.currentGestures.slice(0);
		var needsAnotherMove = false;
		var touch = ev.changedTouches ? ev.changedTouches[0] : ev;
		if(this.startPoint){
			var endPoint = Point.fromEvent(ev);
			if(!(this.lastMovePoint || this.startPoint)["equals"](endPoint)){
				needsAnotherMove = true;
			}
		}
		for(var i = 0, len = gestures.length; i < len; i++){
			var gesture = gestures[i];
			if(!ev.stopped && gesture.listenForEnd){
				// The point has changed, we should execute another onTouchMove before onTouchEnd
				// to deal with the problem of missing events on Androids and alike
				// This significantly improves scrolling experience on Androids!
				if(needsAnotherMove){
					gesture.onTouchMove(ev, touch);
				}
				gesture.onTouchEnd(ev, touch);
			}
			this.stopGesture(gesture);
		}
		if(runtime.isTouch && this.isClick){
			this.isClick = false;
			this.getEventSimulator().fire("click", this.startEvent.target, touch);
		}
		this.lastMovePoint = null;
		this.followTouches = [];
		this.startedChangedTouch = false;
		this.currentTargets = [];
		this.startEvent = null;
		this.startPoint = null;
	};
	this.handleTargets = function(targets, ev){
		// In handle targets we have to first handle all the capture targets,
		// then all the bubble targets.
		this.startedChangedTouch = false;
		this.startedTouches = runtime.isTouch ? ev.touches : [ev];
		for(var i = 0, len = targets.length; i < len; i++){
			if(ev.stopped){
				break;
			}
			this.handleTarget(targets[i], ev, true);
		}
		for(var i = targets.length - 1; i >= 0; i--){
			if(ev.stopped){
				break;
			}
			this.handleTarget(targets[i], ev, false);
		}
		if(this.startedChangedTouch){
			this.followTouches = this.followTouches.concat((runtime.isTouch && ev.targetTouches) ? runtime.toArray(ev.targetTouches) : [ev]);
		}
	};
	this.handleTarget = function(target, ev, capture){
		var gestures = runtime._element.data(target, "gestures") || [];
		for(var i = 0, len = gestures.length; i < len; i++){
			var gesture = gestures[i];
			if(
				(!!gesture.capture === !!capture) &&
				(this.followTouches.length < gesture.touches) &&
				((runtime.isTouch && ev.targetTouches) ? (ev.targetTouches.length === gesture.touches) : true)
			){
				this.startedChangedTouch = true;
				this.startGesture(gesture);
				if(gesture.listenForStart){
					gesture.onTouchStart(ev, ev.changedTouches ? ev.changedTouches[0] : ev);
				}
				if(ev.stopped){
					break;
				}
			}
		}
	};
	this.startGesture = function(gesture){
		gesture.started = true;
		var p = this.currentGestures.indexOf(gesture);
		if(p != -1){
			this.currentGestures.removeAt(p);  //防止重复
		}
		this.currentGestures.push(gesture);
	};
	this.stopGesture = function(gesture){
		gesture.started = false;
		this.currentGestures.remove(gesture);
	};
	this.stopAllGesture = function(ev){
		var n = 0;
		while(this.currentGestures.length > n){
			if(this.currentGestures[n].type == "touch"){
				//[TODO]可能会隐藏一些问题
				this.onTouchEnd(ev);  //让ScrollView组件有机会隐藏滚动条
				//n++;
				continue;
			}
			this.stopGesture(this.currentGestures[n]);
		}
	};
	this.addEventListener = function(el, eventName, listener){
		//console.log("GestureManager::addEventListener");
		//el = Ext.getDom(el);
		var targets = this.targets;
		var name = this.getGestureName(eventName);
		//for debug
		if(!name){
			throw new Error("Trying to subscribe to unknown event " + eventName);
		}
		var gestures = runtime._element.data(el, "gestures");
		if(!gestures){
			gestures = [];
			runtime._element.data(el, "gestures", gestures);
		}
		if(targets.indexOf(el) === -1){
			this.targets.push(el);
		}
		var gesture = this.get(runtime._element.getUid(el) + "-" + name);
		if(!gesture){
			gesture = this.create1({"target": el, "type": name});
			gesture.setManager(this);
			gestures.push(gesture);
			// The line below is not needed, runtime._element.data(el, "gestures") still reference gestures
			// runtime._element.data(el, "gestures", gestures);
		}
		gesture.addListener(eventName, listener);
		// If there is already a finger down, then instantly start the gesture
		if(this.startedChangedTouch && this.currentTargets.contains(el) && !gesture.started){
			this.startGesture(gesture);
			if(gesture.listenForStart){
				gesture.onTouchStart(this.startEvent, this.startedTouches[0]);
			}
		}
	};
	this.removeEventListener = function(el, eventName, listener){
		//el = Ext.getDom(el);
		var name = this.getGestureName(eventName);
		var gestures = runtime._element.data(el, "gestures") || [];
		var gesture = this.get(runtime._element.getUid(el) + "-" + name);
		if(gesture){
			gesture.removeListener(eventName, listener);
			for(var k in gesture.listeners){
				return;
			}
			gesture.dispose();
			gestures.remove(gesture);
			runtime._element.data(el, "gestures", gestures);
		}
	};
	this.getGestureName = function(ename){
		return this.names && this.names[ename];
	};
	this.registerType = function(type, cls){
		var handles = cls.prototype.handles;
		this.types[type] = cls;
		cls[this.typeName] = type;
		if(!handles){
			handles = cls.prototype.handles = [type];
		}
		this.names = this.names || {};
		for(var i = 0, len = handles.length; i < len; i++){
			this.names[handles[i]] = type;
		}
	};
});